iT邦幫忙

2021 iThome 鐵人賽

DAY 24
0

前一篇文章中,我們介紹了 State Flow 以及它的使用方式,本篇將繼續討論 State Flow。

我們知道 SharedFlow 有提供一個函式能夠讓一般的 Flow 轉成 SharedFlow,無獨有偶,State Flow 也有類似的函式,它的名稱你應該不難猜到,它就是 stateIn

stateIn

public fun <T> Flow<T>.stateIn(
    scope: CoroutineScope,
    started: SharingStarted,
    initialValue: T
): StateFlow<T>

如同 sharedIn ,需要傳入 CoroutineScope 來確定這個 Flow 執行的 Scope ,以及 SharingStarted 何時才會啟動 Flow。不一樣的是第三個參數 initialValue ,因為 state flow 是包含初始值的,所以用 stateIn 建立時,也必須要帶入。

使用範例:

class Day24 {
    val scope = CoroutineScope(Job())
    fun stateFlow(): Flow<Int> = flow {
        emit(2)
        emit(3)
    }.stateIn(
        scope,
        SharingStarted.WhileSubscribed(),
        1
    )
}

我們利用 stateIn 將 flow 轉成 StateFlow ,不過因為在 flow 當中也有發射一些值,我們看看會是怎麼樣的結果。

fun main() = runBlocking {
    val day24 = Day24()
    day24.stateFlow().collect { println(it) }
}
1
3

在這邊因為 StateFlow 只會記得最後一個值,所以中間的 2 就被拋棄了。

Flow 可為空

前面的 stateIn 是讓 Flow 轉成 StateFlow,其中需要帶給他一個初始值,假如 Flow 為空時,最少還是會有一個初始值可供使用。

譬如:

val stateFlow3: Flow<Int> = emptyFlow<Int>().stateIn(
        scope,
        SharingStarted.Lazily,
        1
)

那麼我們取用這個 StateFlow 的時候,就只會出現初始值。(因為StateFlow 是唯獨的)

fun main() = runBlocking{
	day24.stateFlow3.collect { println(it) }
}
1

另外一種 stateIn

還有另外一種 stateIn,它本身是不需要初始值的,所以它的初始值就必須要從 Flow 來取得,那麼我們知道 Flow 裏面的 FlowCollector 是一個 suspend 函式,也就是說 Flow 在產生數值的時候有可能會花費比較多的時間,所以如果 stateIn 要依賴 Flow 的值時,stateIn 就必須要是 suspend 函式才行。

它的定義如下:

public suspend fun <T> Flow<T>.stateIn(scope: CoroutineScope): StateFlow<T>

可以發現,他只有一個參數: CoroutineScope ,而且它還是一個 suspend 函式。

使用看看:

suspend fun stateFlow2(): Flow<Int> = flow {
        delay(1000)
        emit(2)
    }.stateIn(scope)

我們在這邊延遲一秒鐘模擬產生數值所花費的時間,如果按照上面的介紹,當我們調用 collect 時,StateFlow 在一秒內應該不會有任何的值,當一秒之後 Flow 產生一個值後,collect 才會有值出現。

fun main() = runBlocking {
    val day24 = Day24()
    day24.stateFlow2().collect { println(it) }
}

stateFlow


StateFlow 以及 MutableStateFlow 介面

StateFlow 的介面

public interface StateFlow<out T> : SharedFlow<T> {
    /**
     * The current value of this state flow.
     */
    public val value: T
}

MutableStateFlow 的介面

public interface MutableStateFlow<T> : StateFlow<T>, MutableSharedFlow<T> {
    /**
     * The current value of this state flow.
     *
     * Setting a value that is [equal][Any.equals] to the previous one does nothing.
     *
     * This property is **thread-safe** and can be safely updated from concurrent coroutines without
     * external synchronization.
     */
    public override var value: T

    /**
     * Atomically compares the current [value] with [expect] and sets it to [update] if it is equal to [expect].
     * The result is `true` if the [value] was set to [update] and `false` otherwise.
     *
     * This function use a regular comparison using [Any.equals]. If both [expect] and [update] are equal to the
     * current [value], this function returns `true`, but it does not actually change the reference that is
     * stored in the [value].
     *
     * This method is **thread-safe** and can be safely invoked from concurrent coroutines without
     * external synchronization.
     */
    public fun compareAndSet(expect: T, update: T): Boolean
}

先從繼承的項目來看, StateFlow 是繼承 SharedFlow,而 MutableStateFlow 除了繼承 StateFlow 外,還多繼承了 MutableSharedFlow。

所以 StateFlow 是 SharedFlow 的特例,而 MutableSharedFlow 則是能夠與 MutableSharedFlow 一樣能夠呼叫 emit 來更新數值。不過由於 StateFlow 只有包含一個數值,所以呼叫 emit 之後,就會直接更新原本 State 的 value。

而裏面的內容則是 StateFlow 的 value 是 val 的,而 MutableStateFlow 則是 var的,且多了一個 compareAndSet()

還記得我們在上一篇中怎麼去建立 StateFlow 以及 MutableStateFlow 的嗎?

private val _state = MutableStateFlow(false)
val state = _state.asStateFlow()

StateFlow.kt 提供了兩個函式,讓我們直接建立 StateFlow 、MutableStateFlow。

public fun <T> MutableStateFlow(value: T): MutableStateFlow<T> = StateFlowImpl(value ?: NULL)
public fun <T> MutableStateFlow<T>.asStateFlow(): StateFlow<T> = ReadonlyStateFlow(this, null)

這邊就不講太多細節的內容了。

小結

StateFlow 是 SharedFlow 的特別用法,它只存一個值,所以適合使用在狀態通知上,所以稱之為 StateFlow。

我們在建立類別時,可以讓更新資料的方法隱藏在類別內,只把讀取的部分暴露給外面使用者,所以我們可以使用 MutableStateFlow 建立一個可以修改的 StateFlow,並且使用 asStateFlow 讓它轉成不可變的 StateFlow 才暴露給外面的人,在對外暴露一個可以更新 MutableStateFlow 的函式即可。

如果想要直接從 Flow 建立 StateFlow ,我們可以使用 stateIn 來將 cold flow 轉成 hot flow,要記得 stateIn 有兩種格式,一種是有初始值的,另一種則是從 Flow 的 emit 取得。

特別感謝

Kotlin Taiwan User Group
Kotlin 讀書會


由本系列文改編的《Kotlin 小宇宙:使用 Coroutine 優雅的執行非同步任務》已經上市囉。

有興趣的讀者歡迎參考:https://coroutine.kotlin.tips/
天瓏書局


上一篇
Day23:Hot Flow - StateFlow
下一篇
Day 25:[Android] 將 LiveData 用 Flow 替代吧
系列文
Coroutine 停看聽30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言